package com.fly.code.process;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.ibator.api.Ibator;
import org.apache.ibatis.ibator.config.IbatorConfiguration;
import org.apache.ibatis.ibator.config.xml.IbatorConfigurationParser;
import org.apache.ibatis.ibator.internal.DefaultShellCallback;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.xml.sax.InputSource;

import com.fly.code.CodeMaker;

public class RunIbatorProgress implements IRunnableWithProgress
{
    private String driver;
    
    private String dburl;
    
    private String username;
    
    private String password;
    
    private String packName;
    
    private String projectDir;
    
    private String projectSrcDir;
    
    private String[] tabName;
    
    private String prefix;
    
    // FreeMarker model
    Map<String, Object> model = new HashMap<String, Object>();
    
    public RunIbatorProgress(String driver, String dburl, String username, String password, String packName, String projectDir, String[] tabName, String prefix)
    {
        super();
        this.driver = driver;
        this.dburl = dburl;
        this.username = username;
        this.password = password;
        this.packName = packName;
        this.projectDir = projectDir;
        this.projectSrcDir = projectDir + "src\\";
        this.tabName = tabName;
        new File(projectSrcDir).mkdirs();
        this.prefix = prefix;
    }
    
    @Override
    public void run(IProgressMonitor monitor)
        throws InvocationTargetException, InterruptedException
    {
        // 在当前目录，创建并运行脚本
        try
        {
            monitor.beginTask("生成代码", IProgressMonitor.UNKNOWN);
            monitor.subTask("自动生成DAO、Map、MODEL 代码中......");
            creatAndRunIbator(driver, dburl, username, password, packName, projectSrcDir, tabName);
            creatTemplateCode(projectDir, packName, tabName);
            monitor.done();
        }
        catch (Exception e)
        {
            throw new InvocationTargetException(e.getCause(), e.getMessage());
        }
    }
    
    // 在当前目录，创建ibator 配置文件
    private String creatIbatorConig(String driver, String dburl, String username, String password, String packName, String projectSrcDir, String[] selection)
    {
        model.put("driver", driver);
        model.put("dburl", dburl);
        model.put("username", username);
        model.put("password", password);
        model.put("packName", packName);
        model.put("projectSrcDir", projectSrcDir);
        model.put("tables", selection);
        model.put("date", new Date());
        return FreeMarkers.renderIbatorConfigTemplate(model);
    }
    
    // 运行 Ibator代码创建程序
    private void creatAndRunIbator(String driver, String dburl, String username, String password, String packName, String projectSrcDir, String[] tabName)
        throws Exception
    {
        // 直接调用ibator IbatorRunner main
        java.util.List<String> warnings = new ArrayList<String>();
        Set<String> contexts = new HashSet<String>();
        Set<String> fullyqualifiedTables = new HashSet<String>();
        StringReader configStr = new StringReader(creatIbatorConig(driver, dburl, username, password, packName, projectSrcDir, tabName));
        InputSource inputSource = new InputSource(configStr);
        IbatorConfigurationParser cp = new IbatorConfigurationParser(warnings);
        IbatorConfiguration config = cp.parseIbatorConfiguration(inputSource);
        DefaultShellCallback callback = new DefaultShellCallback(true);
        Ibator ibator = new Ibator(config, callback, warnings);
        ibator.generate(null, contexts, fullyqualifiedTables);
    }
    
    /**
     * 生成模板代码
     * 
     * @param projectDir
     * @param packName
     * @param tabNames
     * @see [类、类#方法、类#成员]
     */
    private void creatTemplateCode(String projectDir, String packName, String[] tabNames)
    {
        // 遍历map xml文件
        String fatherDir = projectDir + "src\\" + packName.replace(".", "\\");
        String mapper = fatherDir + "\\map\\";
        Set<String> mappers = new HashSet<String>();
        for (File file : new File(mapper).listFiles())
        {
            mappers.add(packName.replace(".", "\\") + "\\map\\" + file.getName());
        }
        model.put("mappers", mappers);
        
        Map<String, Set<String>> templateMap = getFtlPath();
        
        // 生成配置文件
        Set<String> cfg = templateMap.get("cfg");
        for (String path : cfg)
        {
            try
            {
                String fileName = new File(path).getName();
                String newPath = projectDir + fileName.replace("_", "\\").replace(".ftl", "");
                File file = new File(newPath);
                if (!file.exists())
                {
                    String data = FreeMarkers.renderTemplate(model, fileName);
                    FileUtils.writeStringToFile(file, data, "UTF-8");
                }
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
        // 生成模板java文件
        Set<String> java = templateMap.get("java");
        for (String path : java)
        {
            // e.g: java_common_BaseDAO.ftl
            try
            {
                String fileName = new File(path).getName();
                // 需转换为 XXXService、XXXController
                boolean rewrite = false;
                String[] prefixs = {"java_controller_", "java_service_"};
                for (String prefix : prefixs)
                {
                    if (fileName.startsWith(prefix))
                    {
                        rewrite = true;
                        break;
                    }
                }
                if (rewrite)
                {
                    for (String tabName : tabNames)
                    {
                        String className = getCamelCaseString(tabName, true);
                        model.put("className", className);
                        String classPathName = StringUtils.substringBeforeLast(fileName, "_") + "_" + className + StringUtils.substringAfterLast(fileName, "_");
                        String newPath = fatherDir + "\\" + classPathName.substring(5).replace("_", "\\").replace(".ftl", ".java");
                        File file = new File(newPath);
                        if (!file.exists())
                        {
                            String data = FreeMarkers.renderTemplate(model, fileName);
                            FileUtils.writeStringToFile(file, data, "UTF-8");
                        }
                    }
                }
                else
                {
                    for (String tabName : tabNames)
                    {
                        String className = getCamelCaseString(tabName, true);
                        model.put("className", className);
                        String newPath = fatherDir + "\\" + fileName.substring(5).replace("_", "\\").replace(".ftl", ".java");
                        File file = new File(newPath);
                        if (!file.exists())
                        {
                            String data = FreeMarkers.renderTemplate(model, fileName);
                            FileUtils.writeStringToFile(file, data, "UTF-8");
                        }
                    }
                }
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
            
        }
    }
    
    /**
     * 获取模板路径信息
     * 
     * @return
     * @see [类、类#方法、类#成员]
     */
    private Map<String, Set<String>> getFtlPath()
    {
        Map<String, Set<String>> map = new HashMap<>();
        Set<String> cfg = new HashSet<String>();
        Set<String> java = new HashSet<String>();
        URL url = CodeMaker.class.getProtectionDomain().getCodeSource().getLocation();
        if (url.getPath().endsWith(".jar"))
        {
            try
            {
                JarFile jarFile = new JarFile(url.getFile());
                Enumeration<JarEntry> entrys = jarFile.entries();
                while (entrys.hasMoreElements())
                {
                    JarEntry jar = entrys.nextElement();
                    String name = jar.getName();
                    if (name.endsWith(".ftl"))
                    {
                        if (name.startsWith("template/java_"))
                        {
                            java.add(name);
                        }
                        else
                        {
                            cfg.add(name);
                        }
                    }
                }
                jarFile.close();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
        else
        {
            File tFile = new File(url.getFile() + "\\template");
            for (File file : tFile.listFiles())
            {
                String path = file.getAbsolutePath();
                if (path.contains("\\template\\java_"))
                {
                    java.add(path);
                }
                else
                {
                    cfg.add(path);
                }
            }
        }
        map.put("cfg", cfg);
        map.put("java", java);
        return map;
    }
    
    /**
     * 驼峰命名
     * 
     * @param inputString
     * @param firstCharacterUppercase
     * @return
     * @see [类、类#方法、类#成员]
     */
    private String getCamelCaseString(String inputString, boolean firstCharacterUppercase)
    {
        StringBuilder sb = new StringBuilder();
        boolean nextUpperCase = false;
        for (int i = 0; i < inputString.length(); i++)
        {
            char c = inputString.charAt(i);
            switch (c)
            {
                case '_':
                case '-':
                case '@':
                case '$':
                case '#':
                case ' ':
                    if (sb.length() > 0)
                    {
                        nextUpperCase = true;
                    }
                    break;
                default:
                    if (nextUpperCase)
                    {
                        sb.append(Character.toUpperCase(c));
                        nextUpperCase = false;
                    }
                    else
                    {
                        sb.append(Character.toLowerCase(c));
                    }
                    break;
            }
        }
        if (firstCharacterUppercase)
        {
            sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
        }
        return sb.toString();
    }
}